Moisture-aware LAI phenology (LAIType = 2) via Design C (#1292)#1316
Moisture-aware LAI phenology (LAIType = 2) via Design C (#1292)#1316
LAIType = 2) via Design C (#1292)#1316Conversation
Adds dev-ref/design-notes/ for issue-scoped developer design records, plus the first entry: a design proposal for moisture-aware phenology targeting rainfall-driven sites where the purely thermal GDD/SDD scheme collapses to near-zero seasonality (evidence from AU-ASM vs US-MMS in the prior FLUXNET2015 fits). Reviews CLM5, Noah-MP, JULES, ORCHIDEE, LPJ, ISBA and crop-model precedents; recommends a hybrid Jarvis-style stress factor plus CLM5-style persistence trigger behind a new LAIType=2 switch. Also proposes validation against FLUXNET2015 dryland and monsoon sites using the existing Mathematica LAIModel fitting pipeline. No code changes.
Introduces a new LAIType = 2 path for vegetation phenology and threads
the plumbing end-to-end without changing numerical behaviour. The new
branch mirrors LAIType = 0 exactly (bit-identical on the sample run);
the Design C Jarvis + CLM5-persistence-trigger numerics land in PR2.
Fortran physics
- LAI_PRM gains six moisture parameters (w_wilt, w_opt, f_shape,
w_on, w_off, tau_w). Thresholds operate on dimensionless relative
soil water w = 1 - smd/smdcap; tau_w is in days.
- PHENOLOGY_STATE gains three per-veg-surface slots (wbar_id,
w_id_prev, leaf_on_permitted).
- update_GDDLAI signature extended with previous-day SMD (mm), SMD
capacity (mm), the six moisture parameters and the three state
arrays. LAIType == 2 branch is a no-op with TODO(PR2) markers;
the LAI power-law now explicitly routes LAIType == 2 through the
original (LAIType == 0) curve so scaffolding is bit-identical.
Rust/C bridge (schema v1 -> v2)
- LAI_PRM flat length 11 -> 17 in lai.rs and c_api/lai.f95.
- PHENOLOGY_STATE flat length 76 -> 85 in phenology.rs,
c_api/phenology.f95, and c_api/driver.f95:104 aggregate; driver.f95
pack_phenology_state extended for the three new arrays.
- Embedded LAI slices widen LC_DECTR_PRM 61 -> 67, LC_EVETR_PRM
57 -> 63, LC_GRASS_PRM 55 -> 61; shadow types, pack/unpack, and
Rust slice offsets updated on both sides.
- yaml_config.rs honours the six new lai.* keys when overriding.
Python data model
- LAIParams exposes six optional FlexibleRefValue(float) fields and a
laitype=2-only validator that checks w_opt > w_wilt and
w_off < w_on and logs unset moisture fields.
- to_df_state / from_df_state serialise the new columns with safe
defaults so legacy YAMLs keep loading.
- checker_rules_indiv.json adds six grid-level entries so run_supy's
check_state() does not reject the new df_state columns.
Tests and diagnostics
- test/data_model/test_laiparams_moisture_fields.py: four tests
covering default load, explicit round-trip, validator scoping, and
checker-rule coverage.
- test/test_phenology_lai_type_scaffolding.py: bit-identity of
LAIType = 2 vs LAIType = 0 on the bundled sample (the sample
defaults to laitype=1, so the test mutates the initial state
explicitly), plus a no-NaN sanity run for laitype=1.
- scripts/verify/moisture_phenology_site.py: standalone diagnostic
harness for the eventual FLUXNET2015 comparison (AU-ASM by
default); reads from the local FLUXNET archive, caches under
.context/gh1292/<site>/, emits lai_timeseries.png, metrics.json
and summary.txt. Informational in PR1 (V0 == V2); becomes the
scientific acceptance harness in PR2.
Design record
- dev-ref/design-notes/gh1292-moisture-phenology.md (from commit
2a019ba) captures the full design; this PR implements the PR1
slice described there.
…oisture # Conflicts: # CHANGELOG.md
Replaces the PR1 no-op in update_GDDLAI LAItype=2 branch with the
Design C hybrid scheme from the internal design note: a Jarvis-style
water-stress factor on delta_GDD plus a CLM5-style persistence latch
on the tau_w-day running mean of relative soil water.
Fortran physics
- Jarvis f_w: f_w = 0 when w <= w_wilt, f_w = 1 when w >= w_opt, and
a piecewise-power curve on the intermediate interval.
- CLM5 latch: leaf_on_permitted flips off when wbar_id drops below
w_off and flips back on when wbar_id rises above w_on while the
thermal companion delta_GDD > 0 is satisfied. When the latch is
closed, delta_GDD is zeroed out.
- Seed wbar_id from the first measured w (detected when w_id_prev
and wbar_id are both still at their zero defaults) so well-watered
sites do not spend tau_w days ramping up from zero.
- PHENOLOGY_STATE.leaf_on_permitted default flipped from false to
true across Fortran, Rust bridge, and C shadow so mid-year starts
and wet-site configurations do not wait for the persistence
trigger before accumulating GDD.
Tests
- test_phenology_lai_type_scaffolding.py rewritten for PR2
semantics: bit-identity now holds in the well-watered regime
(default thresholds on the Swindon sample), aggressive Jarvis
thresholds strictly reduce mean LAI, and depleting soilstore_surf
to 10 mm on day 1 demonstrably delays spring green-up over the
first 60 days.
- Legacy laitype=1 sanity check preserved.
CHANGELOG entry added; Rust bridge unit tests (lai, phenology) still
pass after the default flip.
Introduces a parameter-sweep tool, documents the calibration
methodology, and runs a sensitivity demo on the bundled Swindon
sample. Does not attempt a full per-IGBP FLUXNET fit (that requires
dedicated per-site SUEWS configurations outside this repo) and does
not add the optional additive moisture-stress SDD term (deferred
until calibration data motivates it).
scripts/verify/moisture_phenology_sweep.py
- Scans one of the six moisture parameters (w_wilt, w_opt, f_shape,
w_on, w_off, tau_w) over user-supplied or default value lists,
reruns SUEWS per value, reports mean LAI, RMSE vs LAIType=0
baseline, seasonal amplitude, green-up DOY, and emits a dual-axis
sensitivity PNG alongside the JSON payload.
- --all flag scans all six parameters sequentially.
- --dry-start depletes vegetation soilstore_surf to 10 mm on day 1
so the moisture gate engages at otherwise well-watered samples
(Swindon sample is UK-suburban).
- Pairwise-validator co-adjustment keeps w_opt > w_wilt and
w_on > w_off satisfied during extreme sweep values.
- Outputs land in .context/gh1292/<site>/sweep_<param>.{json,png}.
dev-ref/design-notes/gh1292-moisture-phenology.md
- New Appendix A covering: sweep-tool usage (A.1); Swindon
sensitivity results and the "w_wilt, w_opt, w_off" sensitive-trio
finding (A.2); FLUXNET2015 calibration roadmap with primary
dryland/monsoon tier and temperate control tier and acceptance
bars from section 8 (A.3); status of the optional SDD
senescence term as deferred (A.4).
test/test_moisture_phenology_sweep.py
- Slow-tagged CLI smoke test that runs a one-point sweep and
validates the JSON schema (parameter name, baseline mean LAI,
five required fields per point).
CHANGELOG entry added for 18 Apr.
Introduces a lightweight system for archiving dashboards and narratives that accompany scientific PRs: - New `docs/source/development/` tree exposed via the main toctree, with a landing page explaining the dev-notes convention. - Sphinx `html_extra_path` now includes `docs/source/_extra/`, so per-PR dashboards placed under `_extra/dev-notes/<slug>/` are copied verbatim into the built site at `/dev-notes/<slug>/`. - `scripts/suews/build_dev_note.py` classifies assets as light (copied in-repo) or heavy (staged for a GitHub Release tagged `dev-notes-<slug>`), rewrites heavy-asset URLs in the source dashboard, and emits the release commands for the operator.
- Adds `.github/pull_request_template.md` with a check-box classifier (Bug / Refactor / Docs / Tooling / Scientific) and a scientific-PR sub-checklist that asks for tests, a dev-note dashboard, and the associated `docs/source/development/<slug>.rst` narrative. - Extends the audit-pr skill with a dedicated Step 3 "Scientific PR dev-note demo" block mirroring the template: reviewers verify that scientific PRs ship with a runnable dashboard under `docs/source/_extra/dev-notes/<slug>/` and a summary page in `docs/source/development/`, and that the dev-notes toctree has been updated.
Fills the newly introduced dev-notes slot for the moisture-aware LAI
phenology work:
- New dashboard + narrative
- `docs/source/development/gh-1292-lai-moisture.rst` summarises the
problem, Design C mechanism, three-PR landing, and links to the
full internal design note.
- `docs/source/_extra/dev-notes/gh-1292-lai-moisture/dashboard.html`
is the self-contained demo (hero metrics, four-panel drought
demo, Design C equations, sensitivity summary, commit timeline).
- Two publication-quality demo figures with a unified panel-label
convention — left edge aligned with the y-axis-label column, two
scenarios (solo letter vs labelled title) sharing one helper:
- `scripts/verify/moisture_phenology_drought_demo.py` — 4-panel
London drought scenario showing the two control branches; solo
`A)`/`B)`/`C)`/`D)` labels.
- `scripts/verify/moisture_phenology_sensitivity_summary.py` —
two-panel sensitivity ranking + dose-response; labelled-title
`A)`/`B)` that concatenate the panel description.
- `moisture_phenology_sweep.py` and the two tests now default to the
London sample (Ward et al. 2016 KCL benchmark); the bundled sample
config was never Swindon.
- Design-note Appendix A updated with the same London-vs-Swindon fix
and the sensitivity-trio finding (`w_wilt`, `w_opt`, `w_off`).
- CHANGELOG entries for PR2 and PR3 rewrite "Swindon" → "London" for
the same reason.
Rolls back 3a272c8 and the GH-1292 dashboard artefacts from 5991fb4. Keeps the scientific substance (verify scripts, tests, CHANGELOG, design note) but drops the archival layer — evidence for scientific PRs lives in GitHub PR comments and linked verify scripts + figures, not in a parallel RST/dashboard pipeline.
…ecklist" Rolls back 9583f47. Scientific PRs do not need a dedicated review track or a dashboard-delivery requirement — ordinary PR description, review, and comments are the right venue for evidence and discussion. Drops the PR template along with the audit-pr Step 3 dev-note section.
The previous PR2 code used 0.0 as the "unset" marker for `wbar_id` and `w_id_prev` on first activation, but 0.0 is a physically valid value of dimensionless relative soil water (completely dry). A site that began a timestep with `w = 0` would wrongly be re-seeded every step instead of advancing the running mean. Switch the unset sentinel to -1.0 (outside the physical [0, 1] range that `w` is clamped to) and check `< 0.0` in `update_GDDLAI`. Applies consistently across: - `PHENOLOGY_STATE%wbar_id`, `%w_id_prev` in `suews_type_vegetation.f95` - `update_GDDLAI` seed-detection in `suews_phys_dailystate.f95` - The `phenology_state_shadow` defaults in the Rust/C bridge (`suews_bridge/c_api/phenology.f95` + `suews_bridge/src/phenology.rs`)
Rewrites `scripts/verify/moisture_phenology_site.py` so the forcing is parsed via SuPy's `read_forcing()` utility (preserving subhourly minute resolution instead of the ad-hoc Y%j%H reconstruction), and so `run_scenario()` takes the forcing and run window as explicit arguments rather than silently picking them up from the sample config. Adds `test/test_moisture_phenology_site.py` covering: - `load_forcing()` preserves 30-min timestamps and filters by year correctly using the new SuPy parser - `run_scenario()` threads the caller-supplied forcing and date window through to SuPy rather than swallowing them
Evidence that moisture-aware LAI (
|
Adds Step 3 description-rigour criteria to audit-pr skill: scientific PRs must expose methodology, scientific decisions, and quantitative results in the PR body or thread, not in a sidecar artefact. Provides triggers, required content, and handling for thin descriptions.
…oisture # Conflicts: # CHANGELOG.md
CI Build PlanChanged FilesFortran source (2 files)
Rust bridge (12 files)
Python source (3 files)
Tests (4 files)
Documentation (2 files)
Build Configuration
Rationale
Updated by CI on each push. See path-filters.yml for category definitions. |
Preview Deployed
Note This preview is ephemeral. It will be lost when:
To restore, push any commit to this PR. |
Adds module-level `pytestmark` to the four GH-1292 test files so the `scripts/lint/check_test_markers.py` CI lint (gh#1300) passes. Data-model and script-harness tests get `api`; the SUEWSSimulation-driven LAI scaffolding test gets `physics`.
|
There should be four LAI approaches already - - so need to understand how new method is 2 - where are the rest? |


Closes #1292
Adds a new
LAIType = 2pathway that modulates thermal-driven phenology with soil-moisture state for rainfall-driven sites, via a Jarvis-style water-stress factor ondelta_GDDplus a CLM5-style persistence latch on a tau-day running mean of relative soil waterw = 1 - SMD / SMDcap.Six new per-vegetation-surface parameters (
w_wilt,w_opt,f_shape,w_on,w_off,tau_w) are threaded through the Fortran type, the Rust/C bridge (LAI_PRM+PHENOLOGY_STATEschema bumped to v2), and the SuPy data model; shipped defaults degrade gracefully to thermal-only behaviour so well-watered sites remain bit-identical toLAIType = 0.Ships with a parameter-sweep calibration tool (
scripts/verify/moisture_phenology_sweep.py --all --dry-start), a two-branch drought demo on the London sample that isolates the moisture control from the thermal driver, and an internal design note atdev-ref/design-notes/gh1292-moisture-phenology.mdwith literature review and the A/B/C/D trade-off analysis.FLUXNET calibration of the sensitive trio (
w_wilt/w_opt/w_off) and the optional SDD water-stress senescence term are deferred to follow-ups (roadmap in Appendix A).Test plan
make devclean rebuild (Fortran + Rust + Python wheel)cargo test --lib --features physics lai phenology— 13/13 passpytest test/test_phenology_lai_type_scaffolding.py test/data_model/test_laiparams_moisture_fields.py test/test_moisture_phenology_sweep.py test/test_moisture_phenology_site.py test/core/test_laimethod.pymake test-smoke— 9/9 passLAIType = 2bit-identical toLAIType = 0on the bundled London sample under defaults